#include "../include/WebPageQuery.h"
#define N _maxId

// WebPageQuery* WebPageQuery::_pInstance = nullptr;

string WebPageQuery::doQuery(const string &strd)
{
    _searchKeys.clear();
    _textIdOrder.clear();
    _textOrder.clear();
    _unionId.clear();
    _textSummary.clear();
    // 获取程序开始运行时的时间点
    auto start_time = std::chrono::high_resolution_clock::now();
    string str = strd;

    // 去掉英文、数字和空格
    str.erase(std::remove_if(str.begin(), str.end(), [](char c)
                             { return std::isalnum(c) || std::isspace(c); }),
              str.end());

    _jieba.Cut(str, _searchKeys); // 将要查询的句子进行分词
    // 去除停用词
    for (auto it = _searchKeys.begin(); it != _searchKeys.end(); ++it)
    {
        auto itt = _stop.find(*it);
        if (itt != _stop.end())
        {
            auto tmp = it;
            _searchKeys.erase(it);
            it = tmp;
        }
    }

    cout << "开始搜索: " << str << endl;

    // 计算分词后的权重
    vector<double> weight = getQueryWordsWeightVector(_searchKeys);

    bool isStore = isStoreInv(_searchKeys);
    if (!isStore) // 所有关键词都不存在
    {
        vector<json> it;
        json a;
        a["items"] = it;
        cout << "搜索完成：未找到欲搜索内容，所有文章均无分词后的关键词。" << endl;
        return a.dump();
    }

    calcuCos(weight);
    obtainSummary();
    // 获取程序结束运行时的时间点
    auto end_time = std::chrono::high_resolution_clock::now();

    // 计算程序运行时间（以毫秒为单位）
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    cout << "搜索完成：共搜索到" << _textOrder.size() << "篇相关文章。" << endl;
    std::cout << "搜索花费时间: " << duration.count() << " 毫秒" << std::endl;
    return createJson();
}

int cmpInt(const pair<int, int> &lhs, const pair<int, int> &rhs)
{
    return lhs.second > rhs.second;
}

bool cmpText(const tuple<int, int, double> &lhs, const tuple<int, int, double> &rhs)
{
    if (std::get<1>(lhs) > std::get<1>(rhs))
    {
        return true;
    }
    else if (std::get<1>(lhs) == std::get<1>(rhs))
    {
        return std::get<2>(lhs) > std::get<2>(rhs);
    }
    else
    {
        return false;
    }
}

vector<double> WebPageQuery::getQueryWordsWeightVector(vector<string> &queryWords)
{
    cout << "分词后结果：";
    for (auto &p : queryWords)
    {
        cout << p << "/";
    }
    cout << endl;

    vector<double> w;

    // 计算IDF=log2(N/(DF+1)) DF：文章中出现次数
    // 计算w = TF * IDF
    auto itt = _df.begin();
    double tf = 1.0 / queryWords.size();
    for (auto it = queryWords.begin(); it != queryWords.end(); ++it)
    {
        itt = _df.find(*it);
        if (itt != _df.end())
        {

            double idf = log2(N / (itt->second + 1));
            double tmpw = tf * idf;
            /*test
            cout << "tf = " << tf << " "
                 << "df = " << itt->second;
            cout << " idf = ";
            cout << idf;
            cout << " tmpw = " << tmpw << endl;
            */
            w.push_back(tmpw);
        }
        else
        {
            double tmpw = tf * log2(N / 1);
            w.push_back(tmpw);
        }
        // 若无关键词 后续可优化？
    }
    double tmp;
    for (auto it = w.begin(); it != w.end(); ++it)
    {
        tmp += *it * *it;
    }
    // cout << "tmp = " << tmp << endl;

    // 计算w'
    for (auto it = w.begin(); it != w.end(); ++it)
    {
        *it = *it / sqrt(tmp);
        // cout << *it << "/";
    }
    // cout << endl;
    return w;
}

void WebPageQuery::loadInv(const string &filename)
{
    ifstream ifs(filename);
    // 日志
    if (!ifs)
    {
        cout << "ifs error" << endl;
        return;
    }

    string ruleLine;
    while (getline(ifs, ruleLine))
    {
        std::istringstream ist(ruleLine);
        string objWords;
        ist >> objWords;
        // 判断是否为文章ID
        string tmp;
        int times = 0;
        while (ist >> tmp)
        {
            int id;
            double weight;
            if (tmp.find(".") == tmp.npos)
            {
                ++times;
                id = std::atoi(tmp.c_str()); // 文章ID
                ist >> tmp;
                weight = std::stod(tmp);
                if (id > _maxId)
                {
                    _maxId = id;
                }
            }
            _invertIndexTable[objWords].insert({id, weight});
        }
        _df.insert({objWords, times});
    }
    ifs.close();
}

void WebPageQuery::loadStop(const string &filename)
{
    ifstream ifs(filename);
    // 日志
    if (!ifs)
    {
        cout << "ifs error" << endl;
        return;
    }

    string ruleLine;
    while (getline(ifs, ruleLine))
    {
        std::istringstream ist(ruleLine);
        string objWords;
        ist >> objWords;
        _stop.insert(objWords);
    }
    ifs.close();
}

bool WebPageQuery::isStoreInv(vector<string> words)
{

    for (auto it = words.begin(); it != words.end(); ++it)
    {
        auto itt = _invertIndexTable.find(*it); // itt 为关键字所在倒排索引行
        if (itt == _invertIndexTable.end())     // 判断是否存在关键字
        {
            return false;
        }

        /* 测试
        cout << "here:";
        for (auto a = itt->second.begin(); a != itt->second.end(); ++a)
        {
            cout << a->first << " ";
        }
        cout << endl;
        */

        // 取各关键字交集
        for (auto ittt = itt->second.begin(); ittt != itt->second.end(); ++ittt)
        {
            // cout << "this" << endl;
            auto a = _unionId.find(ittt->first);
            if (a == _unionId.end())
            {
                _unionId.insert({ittt->first, 1});
            }
            else
            {
                a->second++;
            }
        }
    }

    // 按包含关键字数量排序
    for (auto it = _unionId.begin(); it != _unionId.end(); ++it)
    {
        _textIdOrder.push_back({it->first, it->second});
    }

    std::sort(_textIdOrder.begin(), _textIdOrder.end(), &cmpInt);

    if (_textIdOrder.size() == 0)
    {
        return false;
    }
    return true;
}

void WebPageQuery::calcuCos(const vector<double> &x)
{
    unordered_map<int, vector<double>> _textW;
    for (auto itt = _searchKeys.begin(); itt != _searchKeys.end(); ++itt) // 遍历关键词数组
    {
        auto it = _invertIndexTable.find(*itt); // it:倒排索引中 关键词所在行
        // ittt 为倒排索引中 关键词后第一个pair<ID, weight>
        for (auto ittt = it->second.begin(); ittt != it->second.end(); ++ittt) // 遍历ittt
        {
            _textW[ittt->first].push_back(ittt->second); // 存放文章ID对应的yw
        }
    }
    unordered_map<int, pair<int, double>> _textCos;
    double xy;
    double xyabs;
    // 计算cos
    for (auto it = _textW.begin(); it != _textW.end(); ++it)
    {
        auto ittt = x.begin();
        for (auto itt = it->second.begin(); itt != it->second.end(); ++itt, ++ittt)
        {
            xy += (*itt) * (*ittt);
            xyabs += sqrt(*itt) * sqrt(*ittt);
            // cout << "xy: " << xy << " xyabs: " << xyabs << endl;
        }
        _textCos[it->first].second = xyabs;
        _textCos[it->first].first = _unionId[it->first];
    }

    // 将textid与对应cos放入vector
    for (auto it = _textCos.begin(); it != _textCos.end(); ++it)
    {
        // pair<int, pair<int, double>> tmp = {it->first, {it->second.first, it->second.second}};
        _textOrder.push_back(std::make_tuple(it->first, it->second.first, it->second.second));
    }
    // 按cos值降序排序
    sort(_textOrder.begin(), _textOrder.end(), &cmpText);

    /*test
    for (auto it = _textOrder.begin(); it != _textOrder.end(); ++it)
    {
        cout << "text id: " << std::get<0>(*it) << " times: " << std::get<1>(*it) << " cos: " << std::get<2>(*it) << endl;
    }
    */
}

void WebPageQuery::loadPageLib(const string &filename)
{
    ifstream ifs(filename, std::ios::in);
    // 日志
    if (!ifs)
    {
        cout << "ifs error" << endl;
        return;
    }
    string ruleLine;
    int line = 0;
    while (getline(ifs, ruleLine))
    {
        int tmpLine = line++ % 6;
        int tmpInt;
        if (tmpLine == 0) //<doc>
        {
            continue;
        }
        if (tmpLine == 1) //<docid>
        {
            string tmp = ruleLine;
            auto it = tmp.find("<docid>");
            tmp.erase(it, 7);
            it = tmp.find("</docid>");
            tmp.erase(it, 8);
            tmpInt = std::stoi(tmp);
            _pageLib[tmpInt];
            _textSummary[tmpInt];
        }
        if (tmpLine == 2) //<title>
        {
            string tmp = ruleLine;
            while (1)
            {
                auto a = tmp.find("\t");
                if (a == tmp.npos)
                {
                    break;
                }
                tmp.erase(a, 1);
            }
            while (1)
            {
                auto a = tmp.find(" ");
                if (a == tmp.npos)
                {
                    break;
                }
                tmp.erase(a, 1);
            }
            auto it = tmp.find("<title>");
            tmp.erase(it, 7);
            it = tmp.find("</title>");
            tmp.erase(it, 8);
            _pageLib[tmpInt].push_back(tmp);
        }
        if (tmpLine == 3) //<link>
        {
            string tmp = ruleLine;
            while (1)
            {
                auto a = tmp.find("\t");
                if (a == tmp.npos)
                {
                    break;
                }
                tmp.erase(a, 1);
            }
            while (1)
            {
                auto a = tmp.find(" ");
                if (a == tmp.npos)
                {
                    break;
                }
                tmp.erase(a, 1);
            }
            auto it = tmp.find("<link>");
            tmp.erase(it, 6);
            it = tmp.find("</link>");
            tmp.erase(it, 8);
            _pageLib[tmpInt].push_back(tmp);
        }
        if (tmpLine == 4) // <content>
        {
            string tmp = ruleLine;
            auto it = tmp.find("<content>");
            tmp.erase(it, 9);
            it = tmp.find("</content>");
            tmp.erase(it, 10);
            while (1)
            {
                auto a = tmp.find(" ");
                if (a == tmp.npos)
                {
                    break;
                }
                tmp.erase(a, 1);
            }
            string intTmp = std::to_string(tmpInt);
            _redis.set(intTmp, tmp);
            //_pageLib[tmpInt].push_back(tmp);
        }
        if (tmpLine == 5) //</doc>
        {
            continue;
        }
    }
    ifs.close();
}

// 截取一个字符串的一部分
std::string mySub(const std::string &input, size_t bytePos, size_t length)
{
    // 如果bytePos超出字符串的长度，返回空字符串
    if (bytePos >= input.size())
    {
        return "";
    }

    // 使用std::wstring_convert来处理UTF-8编码的字符串
    std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
    std::u32string utf32 = converter.from_bytes(input);

    // 确保bytePos在合理范围内
    if (bytePos > 0)
    {
        // 计算要跳过的UTF-8字符数量
        size_t utf8Pos = 0;
        size_t skippedBytes = 0;
        while (utf8Pos < input.size() && skippedBytes < bytePos)
        {
            char32_t codePoint = utf32[utf8Pos];
            size_t codePointSize = 1;
            if (codePoint > 0x7F)
            {
                codePointSize = (codePoint <= 0xFFFF ? 3 : 4);
            }
            skippedBytes += codePointSize;
            utf8Pos++;
        }
        bytePos = utf8Pos;
    }

    // 截取指定长度的字符串
    std::u32string result;
    size_t utf8Pos = bytePos;
    while (utf8Pos < input.size() && result.size() < length)
    {
        result.push_back(utf32[utf8Pos]);
        utf8Pos++;
    }

    // 将结果转换回UTF-8编码
    return converter.to_bytes(result);
}

void WebPageQuery::obtainSummary()
{
    for (auto it = _textOrder.begin(); it != _textOrder.end(); ++it)
    {
        string tmpWordId = std::to_string(std::get<0>(*it));
        // auto itt = _pageLib.find(std::get<0>(*it)); // 用文章ID找到_pagelib中ID所在位置
        //  cout << "std::get<0>(*it): " << std::get<0>(*it) << endl;
        //  cout << "itt->second: " << itt->second[3] << endl;
        // string tmp = itt->second[2]; // tmp = 当前文章正文
        string tmp = *(_redis.get(tmpWordId));
        string summary;

        /*
        if (utf8::is_valid(tmp.begin(), tmp.end()))
        {
            cout << "is" << endl;
        }
        else
        {
            cout << "not" << endl;
        }
        */

        for (auto it = _searchKeys.begin(); it != _searchKeys.end(); ++it)
        {
            size_t loc = tmp.find(*it);
            int front;
            int rear;
            if (loc != tmp.npos) // 正文有关键词
            // 使用substr截取子串时会有乱码
            // 经查询为substr默认为ASCII码
            {
                // 动态摘要
                // 取关键词前后各20字符，共40字符
                front = loc - 20;
                rear = loc + 20;
                if (front > 0 && rear < tmp.size())
                {
                    front = loc - 20;
                    summary += mySub(tmp, front, 40 * 3) + "...";
                }
                // 若关键词为句首，则取关键词后40字符
                else if (loc == 0)
                {
                    front = loc;
                    summary += mySub(tmp, front, 40) + "...";
                    // cout << " 2 ";
                }
                // 若关键词为句末，则取关键词前40字符
                else if (loc == tmp.size())
                {
                    front = loc - 40 * 3;
                    summary += mySub(tmp, front, 40) + "...";
                    // cout << "3 ";
                }
                // 若关键词前不够20字符，则取句首到关键词，并从关键词后取剩余字符
                else if (front < 0 && rear < tmp.size())
                {
                    front = 0;
                    summary += mySub(tmp, front, 40 * 3 - loc) + "...";
                    // cout << "4";
                }
                // 若关键词前够20字符，则取到句尾
                else if (front > 20 && rear > tmp.size())
                {
                    front = loc - 20 * 3;
                    summary += mySub(tmp, front, tmp.size() - 1) + "...";
                    // cout << "5" ;
                }
                // 句首句尾都不够，取全句
                else
                {
                    summary += mySub(tmp, 1, loc + 10) + "...";
                    // cout << "6z" << endl;
                }

                size_t redLoc = summary.find(*it);
                if (redLoc == summary.npos)
                    break;
                string markRed = "\033[38;5;1m";
                summary.insert(redLoc, markRed);
                size_t recoverLoc = summary.find(*it) + (*it).size();
                if (recoverLoc == summary.npos)
                    break;
                string markRecover = "\033[0m";
                summary.insert(recoverLoc, markRecover);
                //cout << "summary: " << summary << endl;
            }
        }

        // string tmpSummary = removeStr(summary);
        // cout << "summary: " << summary << endl;
        /*
        if (utf8::is_valid(summary.begin(), summary.end()))
        {
            cout << "is2"itt[2].clear(); << endl;
        }
        else
        {
            cout << "not2" << endl;
        }
        */
        _textSummary[std::get<0>(*it)] = summary;
        //_pageLib[std::get<0>(*it)].push_back(summary);
    }
}

string WebPageQuery::createJson()
{

    vector<json> items;
    int i = 0;
    for (auto it = _textOrder.begin(); it != _textOrder.end(); ++it, ++i)
    {
        auto itt = _pageLib.find(std::get<0>(*it))->second.begin();
        //cout << itt[2] << endl;
        json item = {
            {"title", itt[0]},
            {"link", itt[1]},
            {"content", _textSummary[std::get<0>(*it)]} // 摘要
        };
        items.push_back(item);
    }
    /*
    std::ofstream ofs("searchResult.json");
    for (int j = 0; j < i; ++j) // json底层采用map，会导致插入顺序改变，需要改变底层容器
    {
        ofs << items[j].dump(4) << endl;
    }
    ofs.close();
    */
    json a;
    a["items"] = items;
    return a.dump();
}

/* int main()
{
    string inv = "../data/invertindex.dat";
    string cnDic = "../data/CnDict.data";
    string enDic = "../data/EnDict.data";
    string cnStopFile = "../data/stop_words_zh.txt";
    string enStopFile = "../data/stop_words_eng.txt";
    string pageLibFile = "../data/pageLib.dat";
    WebPageQuery test(inv, cnDic, enDic, cnStopFile, enStopFile, pageLibFile);
    // test.writeFile("./test.dat");
    // 还原干扰 有意案件
    // string re = test.doQuery("还原干扰");
    // test.writeFile("test.dat");
    // cout << re << endl;
    cout << endl;
    // sleep(5);
    string test1 = test.doQuery("中国");
}
 */